package com.gitee.wsl.transform.validator.bean

import com.gitee.wsl.transform.validator.Validator
import com.gitee.wsl.transform.validator.validateWithResult
import kotlin.jvm.JvmName
import kotlin.reflect.KProperty1

/**
 * Validate an object
 *
 * Represents the function that validating any Kotlin object
 * If any constraint is violated, a [ConstraintViolationException] will be thrown
 *
 * @param block specifies the function DSL
 * @receiver the object to be validated
 * @return the same receiver object
 * @throws ConstraintViolationException
 *
 * @author Rodolpho S. Couto
 * @since 0.1.0
 */
inline fun <E> E.validate(block: BeanValidator<E>.(E) -> Unit): Result<E> {
    val beanValidator = BeanValidator(this).apply { block( this@validate ) }
    if (beanValidator.constraintViolations.isNotEmpty()) {
        return Result.failure(ConstraintViolationException(beanValidator.constraintViolations))
    }
    return Result.success(this)
}


/**
 * Represents the DSL class that contains validation functions
 *
 * @param obj specifies the object to be validated
 * @property constraintViolations specifies the set of [ConstraintViolation]
 * @constructor creates new DSL object
 *
 * @author Rodolpho S. Couto
 * @see Constraint
 * @see ConstraintViolation
 * @since 0.1.0
 */
open class BeanValidator<E>(private val obj: E) {

    /**
     * Specifies the violated constraints
     */
    val constraintViolations = mutableSetOf<ConstraintViolation>()

    /**
     * Returns a [Property] for this property
     *
     * @receiver the property to be validated
     * @return the property validator
     */
    @JvmName("validate")
    fun <T> validate(property: KProperty1<E, T?>): Property<T?> = Property(obj, property)

    fun <T> KProperty1<E, T?>.check(block:Property<T?>.() -> Unit ) = validate(this).block()

    fun <T,V:Validator<T,V>> KProperty1<E, T>.check(rule: Validator<T,V>) = check {
        rule.validateWithResult(get(obj)).onFailure {
            validate(ThrowAbleConstraint(it)){false}
        }
    }

    /**
     * Returns a [Property] for this iterable property
     *
     * @receiver the property to be validated
     * @return the property validator
     */
    @JvmName("validateIterable")
    fun <T> validate(property: KProperty1<E, Iterable<T>?>): Property<Iterable<T>?> = Property(obj, property)

    /**
     * Returns a [Property] for this array property
     *
     * @receiver the property to be validated
     * @return the property validator
     */
    @JvmName("validateArray")
    fun <T> validate(property: KProperty1<E, Array<T>?>): Property<Array<T>?> = Property(obj, property)

    /**
     * Represents a property validator that contains extended functions
     *
     * @param obj specifies the object to be validated
     * @param property specifies the property to be validated
     *
     * @author Rodolpho S. Couto
     * @see BeanValidator
     * @see KProperty1
     * @since 0.1.0
     */
    open inner class Property<T>(val obj: E, val property: KProperty1<E, T?>) {

        /**
         * Validates the property by passing the constraint and the validation function
         *
         * This function is used by all constraint validations
         *
         * @param constraint specifies the function that returns the constraint to be validated
         * @param isValid specifies the validation function
         * @return the property validator
         */
        fun validate(constraint: (T?) -> Constraint, isValid: (T?) -> Boolean): Property<T> {
            val value = this.property.get(this.obj)
            if (!isValid(value)) {
                this@BeanValidator.constraintViolations += DefaultConstraintViolation(
                    property = this.property.name,
                    value = value,
                    constraint = constraint(value)
                )
            }
            return this
        }

        /**
         * Validates the property by passing the constraint and the validation function
         *
         * This function is used by all constraint validations
         *
         * @param constraint specifies the constraint that will be validated
         * @param isValid specifies the validation function
         * @return the property validator
         */
        fun validate(constraint: Constraint, isValid: (T?) -> Boolean): Property<T> =
            validate({ constraint }, isValid)

        /**
         * Validates the property by passing the constraint and the suspending validation function
         *
         * This function is used by all constraint validations
         *
         * @param constraint specifies the function that returns the constraint to be validated
         * @param isValid specifies the validation function
         * @return the property validator
         */
        suspend fun coValidate(constraint: (T?) -> Constraint, isValid: suspend (T?) -> Boolean): Property<T> {
            val value = this.property.get(this.obj)
            if (!isValid(value)) {
                this@BeanValidator.constraintViolations += DefaultConstraintViolation(
                    property = this.property.name,
                    value = value,
                    constraint = constraint(value)
                )
            }
            return this
        }

        /**
         * Validates the property by passing the constraint and the suspending validation function
         *
         * This function is used by all constraint validations
         *
         * @param constraint specifies the constraint that will be validated
         * @param isValid specifies the validation function
         * @return the property validator
         */
        suspend fun coValidate(constraint: Constraint, isValid: suspend (T?) -> Boolean): Property<T> =
            coValidate({ constraint }, isValid)

        /**
         * Adds the constraint violations to property
         *
         * @param constraintViolations specifies the constraint violations
         */
        fun addConstraintViolations(constraintViolations: Iterable<ConstraintViolation>) {
            this@BeanValidator.constraintViolations += constraintViolations
        }
    }
}